﻿//+--------------------------------------------------------------------------------------------------
//     
//Description: Validation Helpers
//Author: Aravind Ramarathinam (aravr@microsoft.com)
//Copyright © Microsoft Corporation
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 
//
//THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, 
//INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. 
//See the Apache Version 2.0 License for specific language governing permissions and limitations under the License.
// 
//--------------------------------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;

namespace VmroleAuthor.Models
{
    public static class ValidationHelper
    {
        public enum PayloadType
        {
            WebDeployPackage,
            ScriptFolder,
            SqlDac,
            SqlScript,
            SQLConfigurationFile
        };

        public static bool IsPayloadTypeFolder(PayloadType payloadType)
        {
            return payloadType == PayloadType.ScriptFolder;
        }

        public static string ValidateVersionFormat(string version, bool requireAllFourFields = true)
        {           
            if (version == null)
            {
                return "Version not specifed";
            }
            Version validatedVersion;
            if (!Version.TryParse(version, out validatedVersion))
            {
                return "Invalid version format";
            }
            if (requireAllFourFields && (validatedVersion.Major == -1 || validatedVersion.Minor == -1 || validatedVersion.MajorRevision == -1 || validatedVersion.Build == -1))
            {
                return "Version must contain all the four fields; Major.Minor.Build.MajorRevision";
            }
            return null;
        }

        // This is needed because Enum.TryParse silently accepts string integer numerals
        public static string ValidateEnum(string str,Type enumType, bool caseInSensitive = false) 
        {
            if (!enumType.IsEnum)
            {
                throw new ArgumentException("Must be enum type");
            }
            if (Enum.GetNames(enumType).Any(name => String.Equals(name, str, caseInSensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)))
            {
                return null;
            }
            return "Invalid value for " + enumType.Name;
        }

        public static string ValidateGuid(string str)
        {
            Guid result;
            if (Guid.TryParse(str, out result))
            {
                return null;
            }
            return "Invalid guid format";
        }

        public static string ValidateNotNullOrEmpty(string str)
        {
            if (String.IsNullOrEmpty(str))
            {
                return "Cannot be null or empty";
            }
            return null;
        }


        public static string ValidateIcon(string iconPath, string workindDir, string expectedFileName, int expectedHeight, int expectedWidth)
        {
            if (!string.IsNullOrEmpty(iconPath))
            {
                string resolvedfullPath = Path.IsPathRooted(iconPath) ? iconPath : Path.Combine(workindDir, iconPath);
                if (!File.Exists(resolvedfullPath))
                {
                    string fmtError = string.Format("No icon with  path {0} exists", resolvedfullPath);
                    return fmtError;
                }

                // If not using relative path, check if icon has been copied to datapackage working dir location   
                string expectedLocation = Path.Combine(workindDir, expectedFileName);
                if (!File.Exists(expectedLocation))
                {
                    return "Press update to copy icon to working dir location: " + expectedLocation;
                }
                using (System.Drawing.Image image = System.Drawing.Image.FromFile(expectedLocation))
                {
                    int actualWidth = image.Width;
                    int actualHeight = image.Height;
                    if (actualHeight != expectedHeight)
                    {
                        string fmtError = string.Format("Expected height of Icon file {0} is {1}. Actual Height is {2}", expectedFileName, expectedHeight, actualHeight);
                        return fmtError;
                    }
                    if (actualWidth != expectedWidth)
                    {
                        string fmtError = string.Format("Expected width of Icon file {0} is {1}. Actual Width is {2}", expectedFileName, expectedWidth, actualWidth);
                        return fmtError;
                    }
                }
            }
            else
            {
                return string.Format("Expected Icon file {0} ({1} x {2}) does not exist", expectedFileName,
                                     expectedHeight, expectedWidth);
            }
            return null;
        }


        public static string ValidateTags(string newTagToBeAdded, List<string> osVhdRequirements)
        {
            string error;
            if (ParameterizationHelper.IsFullyParameterized(newTagToBeAdded))
            {
                error = "Cannot parameterize tags";
                return error;
            }
            if (osVhdRequirements == null)
            {
                return null;
            }
            if (osVhdRequirements.FirstOrDefault(x => string.Equals(x, newTagToBeAdded, StringComparison.Ordinal)) != null)
            {
                error = "Tag already added";
                return error;
            }
            if ((RecommendedTags.GeneralTags.Contains(newTagToBeAdded)) &&
               (RecommendedTags.GeneralTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
                )
            {
                error = "General OS tag already added";
                return error;
            }
            if ((RecommendedTags.MajorVersionTags.Contains(newTagToBeAdded)) &&
                (RecommendedTags.MajorVersionTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
                )
            {
                error = "Major Version tag already added";
                return error;
            }
            if (RecommendedTags.ArchitectureTags.Contains(newTagToBeAdded) &&
                (RecommendedTags.ArchitectureTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
                )
            {
                error = "Architecture tag already added";
                return error;
            }
            if (RecommendedTags.ReleaseRelatedTags.Contains(newTagToBeAdded) &&
                (RecommendedTags.ReleaseRelatedTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
                )
            {
                error = "Release related tag already added";
                return error;
            }
            if (RecommendedTags.ReleaseModifierTags.Contains(newTagToBeAdded) &&
               (RecommendedTags.ReleaseModifierTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
               )
            {
                error = "Release modifier tag already added";
                return error;
            }
            if (RecommendedTags.SQLServerTags.Contains(newTagToBeAdded) &&
                (RecommendedTags.SQLServerTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
                )
            {
                error = "SQL Server tag already added";
                return error;
            }
            if (RecommendedTags.PrerequistiesTags.Contains(newTagToBeAdded) &&
                (RecommendedTags.PrerequistiesTags.Any(tag => osVhdRequirements.FirstOrDefault(x => string.Equals(x, tag, StringComparison.OrdinalIgnoreCase)) != null))
                )
            {
                error = "Prerequisties tag already added";
                return error;
            }
            return null;
        }


        public static string ValidateApplicationDeploymentNamesAreUnique(VMRoleResourceExtensionDefinition resourceExtensionDefinition)
        {
            if (resourceExtensionDefinition == null || 
                resourceExtensionDefinition.ExtensionSettings == null || 
                resourceExtensionDefinition.ExtensionSettings.ApplicationProfile == null || 
                resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile == null)
            {
                return null;
            }
            HashSet<string> appDeploymentNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

            if (resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.WebDeployApplications != null)
            {
                foreach (WebDeployApplication webDeployApplication in resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.WebDeployApplications)
                {
                    if (string.IsNullOrEmpty(webDeployApplication.DeploymentName))
                    {
                        return "Deployment Name cannot be null or empty";
                    }
                    if (appDeploymentNames.Contains(webDeployApplication.DeploymentName))
                    {
                        return string.Format("Deployment Name: {0} not unique across application deployments in resource extension.", webDeployApplication.DeploymentName);
                    }
                    appDeploymentNames.Add(webDeployApplication.DeploymentName);
                }
            }
            if (resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.ScriptApplications != null)
            {
                foreach (ScriptApplication scriptApplication in resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.ScriptApplications)
                {
                    if (string.IsNullOrEmpty(scriptApplication.DeploymentName))
                    {
                        return "Deployment Name cannot be null or empty";
                    }
                    if (appDeploymentNames.Contains(scriptApplication.DeploymentName))
                    {
                        return string.Format("Deployment Name: {0} not unique across application deployments in resource extension.", scriptApplication.DeploymentName);
                    }
                    appDeploymentNames.Add(scriptApplication.DeploymentName);
                }
            }
            if (resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.SQLDacApplications != null)
            {
                foreach (SQLDacApplication sqlDacApplication in resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.SQLDacApplications)
                {
                    if (string.IsNullOrEmpty(sqlDacApplication.DeploymentName))
                    {
                        return "Deployment Name cannot be null or empty";
                    }
                    if (appDeploymentNames.Contains(sqlDacApplication.DeploymentName))
                    {
                        return string.Format("Deployment Name: {0} not unique across application deployments in resource extension.", sqlDacApplication.DeploymentName);
                    }
                    appDeploymentNames.Add(sqlDacApplication.DeploymentName);
                }
            }
            return null;
        }


        public static string ValidateOSProfileLinkage(VMRoleResourceExtensionDefinition resourceExtensionDefinition, ResourceDefinition resourceDefinition)
        {
            string error ;
            if (resourceExtensionDefinition == null)
            {
                return null;
            }
            if (resourceDefinition == null || 
                resourceDefinition.IntrinsicSettings == null ||
                resourceDefinition.IntrinsicSettings.OperatingSystemProfile == null ||
                resourceExtensionDefinition.ExtensionSettings == null ||
                resourceExtensionDefinition.ExtensionSettings.ApplicationProfile == null)
            {
                error  = "One ore more fields unexpectedly null while validating OS Profile specified in Resource Definition and referenced in Resource Extension's Application Profile";
                return error;
            }
            if (resourceDefinition.IntrinsicSettings.OperatingSystemProfile.WindowsOperatingSystemProfile != null)
            {
                if (resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile == null)
                {
                    error = "Windows OS Profile is specified in Resource Definition but referenced Resource Extension's Application Profile does not specify Windows Application Profile";
                    return error;
                }
            }
            else if (resourceDefinition.IntrinsicSettings.OperatingSystemProfile.LinuxOperatingSystemProfile != null)
            {
                if (resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.LinuxApplicationProfile == null)
                {
                    error = "Linux OS Profile is specified in Resource Definition but referenced Resource Extension's Application Profile does not specify Linux Application Profile";
                    return error;
                }
            }
            return null;
        }

        public static string ValidateDeploymentOrderUniquenessForProvisioningScripts(VMRoleResourceExtensionDefinition resourceExtensionDefinition, ProvisioningScript provScriptUnderTest)
        {            
            if (resourceExtensionDefinition == null ||
                resourceExtensionDefinition.ExtensionSettings == null ||
                resourceExtensionDefinition.ExtensionSettings.ApplicationProfile == null ||
                resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile == null)
            {
                return null;
            }
         
            Dictionary<ProvisioningScriptType, HashSet<int>> provisioningScriptDeploymentOrders = new Dictionary<ProvisioningScriptType, HashSet<int>>(); 
            if (resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.ProvisioningScripts!= null)
            {
                foreach (ProvisioningScript provisioningScript in resourceExtensionDefinition.ExtensionSettings.ApplicationProfile.WindowsApplicationProfile.ProvisioningScripts)
                {
                    int deploymentOrder;
                    if (string.IsNullOrEmpty(provisioningScript.DeploymentOrder))
                    {
                        deploymentOrder = 1; // Default
                    }
                    else
                    {
                        if (!int.TryParse(provisioningScript.DeploymentOrder, out deploymentOrder))
                        {
                            return "Invalid integer specified as DeploymentOrder for Provisioning Script";
                        }
                    }                   
                    ProvisioningScriptType provisioningScriptType;
                    if (provisioningScript.ScriptType != null && Enum.TryParse(provisioningScript.ScriptType, out provisioningScriptType))
                    {
                        if (!provisioningScriptDeploymentOrders.ContainsKey(provisioningScriptType))
                        {
                            provisioningScriptDeploymentOrders.Add(provisioningScriptType, new HashSet<int>());
                        }

                        if (provisioningScriptDeploymentOrders[provisioningScriptType].Contains(deploymentOrder) && provScriptUnderTest == provisioningScript)
                        {
                            return string.Format("DeploymentOrder {0} for Provisioning Script Type: {1} is not unique ", deploymentOrder, provisioningScriptType);                         
                        }
                        provisioningScriptDeploymentOrders[provisioningScriptType].Add(deploymentOrder);
                    }
                }
            }          
            return null;
        }


        public static string ValidateDeploymentOrderUniquenessForScriptCommandsInAnApplication(List<ProvisioningScript> scriptCommands, ProvisioningScript provScriptUnderTest)
        {          
            if (scriptCommands == null)
            {
                return null;
            }

            Dictionary<ProvisioningScriptType, HashSet<int>> provisioningScriptDeploymentOrders = new Dictionary<ProvisioningScriptType, HashSet<int>>();
            foreach (ProvisioningScript provisioningScript in scriptCommands)
            {
                int deploymentOrder;
                if (string.IsNullOrEmpty(provisioningScript.DeploymentOrder))
                {
                    deploymentOrder = 1; // Default
                }
                else
                {
                    if (!int.TryParse(provisioningScript.DeploymentOrder, out deploymentOrder))
                    {
                        return "Invalid integer specified as DeploymentOrder for Script Command inside an application";
                    }
                }
                ProvisioningScriptType provisioningScriptType;
                if (provisioningScript.ScriptType != null && Enum.TryParse(provisioningScript.ScriptType, out provisioningScriptType))
                {
                    if (!provisioningScriptDeploymentOrders.ContainsKey(provisioningScriptType))
                    {
                        provisioningScriptDeploymentOrders.Add(provisioningScriptType, new HashSet<int>());
                    }

                    if (provisioningScriptDeploymentOrders[provisioningScriptType].Contains(deploymentOrder) &&  provisioningScript == provScriptUnderTest)
                    {
                        return string.Format("DeploymentOrder {0} for Application Script Command Script Type: {1} is not unique ", deploymentOrder, provisioningScriptType);
                    }
                    provisioningScriptDeploymentOrders[provisioningScriptType].Add(deploymentOrder);
                }
            }
         
            return null;
        }
    }
}
